﻿using Catel.IoC;
using Catel.Messaging;
using CommandLine;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Windows.Win32;
using YtbDownloader.Core.Common;
using YtbDownloader.Core.Interfaces;
using YtbDownloader.Core.Options;

namespace YtbDownloader.Core.Downloaders;

public class YDownloader : IDownloader
{
    private Process _process;
    private readonly IMessageMediator _mediator;
    private const string AudioFormat = "bestaudio[ext=m4a]/bestaudio";
    private const string VideoFormat = "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best";
    private const string NoPlaylistOutputTemplate = "%(title)s.%(ext)s";
    private const string PlaylistOutputTemplate = "%(playlist)s/%(title)s.%(ext)s";

    public bool IsBusy { get; private set; }

    private void OnStart()
    {
        IsBusy = true;
        _mediator.SendMessage(new CommandEvent(EventType.Started));
    }

    private void OnComplete()
    {
        IsBusy = false;
        _mediator.SendMessage(new CommandEvent(EventType.Completed));
    }

    private void OnLogReceived(string data)
    {
        _mediator.SendMessage(new CommandEvent(EventType.Log, data));
    }

    public YDownloader()
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
        _mediator = ServiceLocator.Default.ResolveType<IMessageMediator>();
    }

    public async Task Cancel()
    {
        if (!IsBusy) return;
        _ = PInvoke.AttachConsole((uint)_process.Id);
        _ = PInvoke.SetConsoleCtrlHandler(null, true);
        _ = PInvoke.GenerateConsoleCtrlEvent(0, 0);
        await _process.WaitForExitAsync();
        _ = PInvoke.FreeConsole();
        _ = PInvoke.SetConsoleCtrlHandler(null, false);
    }

    public async Task Download(IConfig config)
    {
        if (config == null) return;
        InitializeTaskY(config);
        if (config.IsEnableExtraArugments && !string.IsNullOrWhiteSpace(config.ExtraArugments))
        {
            _process.StartInfo.Arguments += $" {config.ExtraArugments}";
        }

        await TaskStart();
    }

    private void InitializeTaskY(IConfig config)
    {
        InitializeTask(new OptionY
        {
            IsDebug = config.IsDebug,
            NoPlaylist = !config.IsPlaylist,
            DownloadUrl = config.DownloadUrl,
            Format = config.IsAudioOnly ? AudioFormat : VideoFormat,
            Proxy = config.IsProxy ? config.ProxyUrl : null,
            SubLang = config.IsDownloadSub ? config.SubLang : null,
            OutputTemplate = config.IsPlaylist ?
                Path.Combine(config.OutputPath, PlaylistOutputTemplate) :
                Path.Combine(config.OutputPath, NoPlaylistOutputTemplate),
            IsIgnoreError = config.IsIgnoreError,
            Cookies = config.IsPassCookies ? config.Cookies : null,
            UserAgent = config.IsCustomUserAgent ? config.UserAgent : null
        });
    }

    private void InitializeTask<T>(T option)
    {
        _process = new Process
        {
            EnableRaisingEvents = true,
            StartInfo = new ProcessStartInfo
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                Arguments = Parser.Default.FormatCommandLine(option),
                FileName = "yt-dlp"
            }
        };
        _process.OutputDataReceived += Process_DataReceived;
        _process.ErrorDataReceived += Process_DataReceived;
        _process.Exited += Process_Exited;
    }

    private async Task TaskStart()
    {
        if (_process == null) return;
        OnStart();

        try
        {
            _process.Start();
            _process.BeginOutputReadLine();
            _process.BeginErrorReadLine();
            await _process.WaitForExitAsync();
        }
        catch (System.ComponentModel.Win32Exception e)
        {
            OnLogReceived(e.Message);
            OnComplete();
        }
    }

    private void Process_DataReceived(object sender, DataReceivedEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(e.Data))
        {
            OnLogReceived(e.Data);
        }
    }

    private void Process_Exited(object sender, EventArgs e)
    {
        _process.Dispose();
        OnComplete();
    }
}